home *** CD-ROM | disk | FTP | other *** search
- /*
- * (c) Copyright 1993 by Panagiotis Tsirigotis
- * All rights reserved. The file named COPYRIGHT specifies the terms
- * and conditions for redistribution.
- */
-
- static char RCSid[] = "$Id: ostimer.c,v 4.2 1993/05/06 06:46:14 panos Exp $" ;
-
- #include <stdio.h>
- #include <varargs.h>
-
- extern int errno ;
-
- #include "timemacros.h"
- #include "defs.h"
- #include "impl.h"
- #include "ostimer.h"
-
-
- /*
- * Case 1: DEBUG defined
- * SET_OSTIMER checks for zero interval and terminates in case of setitimer
- * error
- * Case 2: DEBUG undefined, NO_TERMINATION undefined
- * SET_OSTIMER terminates in case of setitimer error
- * Case 3: DEBUG undefined, NO_TERMINATION undefined
- * SET_OSTIMER ignores setitimer error
- */
- #ifdef DEBUG
- #undef NO_TERMINATION
- #define CHECK_INTERVAL( func, itv ) \
- if ( TV_ISZERO( (itv).it_value ) ) \
- terminate( "TIMER %s: zero interval\n", func ) ;
- #else
- #define CHECK_INTERVAL( func, itv )
- #endif
-
- #ifndef NO_TERMINATION
- #define OSTIMER_SET( func, otp, itv ) \
- if ( setitimer( (otp)->ost_systype, &(itv), ITIMERVAL_NULL ) == -1 ) \
- terminate( "TIMER %s: setitimer failed. errno = %d\n", func, errno )
- #else
- #define OSTIMER_SET( func, otp, itv ) \
- (void) setitimer( (otp)->ost_systype, &(itv), ITIMERVAL_NULL )
- #endif
-
- #define SET_OSTIMER( otp, itv, func ) CHECK_INTERVAL( func, itv ) \
- OSTIMER_SET( func, otp, itv )
-
-
- #ifndef NO_TERMINATION
-
- PRIVATE void terminate( fmt, va_alist )
- char *fmt ;
- va_dcl
- {
- va_list ap ;
-
- va_start( ap ) ;
- (void) vfprintf( stderr, fmt, ap ) ;
- abort() ;
- _exit( 1 ) ;
- }
-
- #endif /* ! NO_TERMINATION */
-
-
- /*
- * Initialize the fields of struct timer that are used by the ostimer code
- */
- int __ostimer_newtimer( tp, type )
- timer_s *tp ;
- enum timer_types type ;
- {
- ostimer_s *otp = __ostimer_init( tp, type ) ;
-
- if ( otp == OSTIMER_NULL )
- return( TIMER_ERR ) ;
-
- tp->t_ostimer = otp ;
- TV_ZERO( tp->t_expiration ) ;
- TV_ZERO( tp->t_interval ) ;
- tp->t_count = 0 ;
- return( TIMER_OK ) ;
- }
-
-
-
-
- /*
- * The following variables are used to handle the following scenario:
- *
- * 1. INTERRUPT happens ==> ostimer_interrupt called
- * 2. Timers A and B expire.
- * 3. The function associated with A is invoked and running
- * 4. INTERRUPT happens ==> ostimer_interrupt called
- * 5. Timer C expires.
- * 6. Function of timer C invoked and returns with a jmp_buf.
- *
- * If we longjmp to the jmp_buf returned by the function of timer C the
- * function of timer B will never be called and the function of timer A
- * will never finish.
- * What we do instead is have ostimer_interrupt check the call_level
- * and if greater than 1, then just save the jmp_buf returned by the
- * function of timer C (only if there is no other ret_env) and simply return.
- *
- * Notice that there can be only one ret_env (since there is only 1 control
- * flow).
- *
- * XXX: this complexity stems from the fact that we allow interrupts while
- * the timer functions are running. Is there a good reason for this ?
- * (probably because we have to allow interrupts of other timer types).
- */
- static int call_level ;
- static int have_env ;
- static jmp_buf ret_env ;
-
- #define MAX_EXPIRED 20
-
- /*
- * ostimer_interrupt invokes the functions of the timers that expired.
- * Since we don't know in advance how many timers expired, we follow a
- * two-step procedure:
- *
- * Step 1: collect all expired timers
- * Step 2: invoke timer actions
- *
- * The expired timers are collected in an array stored on the stack.
- * If the array overflows, we arrange for another timer interrupt as
- * soon as possible to service remaining timers. The reason we don't
- * allocate the array using malloc is that malloc is not guaranteed
- * to be reentrant and tracking timing-related dynamic memory allocation
- * problems is guaranteed to be a nightmare.
- *
- * Notice that *all* timer interrupts are blocked during step 1.
- */
- void __ostimer_interrupt( otp )
- register ostimer_s *otp ;
- {
- struct timeval current_time ;
- register timer_s *tp ;
- timer_s *expired_timers[ MAX_EXPIRED ] ;
- unsigned n_expired = 0 ;
- int i ;
-
- if ( timer_pq_head( otp->ost_timerq.tq_handle ) == TIMER_NULL )
- return ;
-
- call_level++ ;
-
- (*otp->ost_get_current_time)( ¤t_time ) ;
-
- /*
- * Get all timers that expired
- */
- for ( ;; )
- {
- tp = timer_pq_head( otp->ost_timerq.tq_handle ) ;
-
- if ( tp == TIMER_NULL || TV_GT( tp->t_expiration, current_time ) ||
- n_expired == MAX_EXPIRED )
- break ;
-
- tp = timer_pq_extract_head( otp->ost_timerq.tq_handle ) ;
- if ( tp->t_state == TICKING )
- {
- tp->t_state = INACTIVE ;
-
- tp->t_count++ ;
- if ( tp->t_blocked )
- {
- if ( tp->t_act == IDLE )
- tp->t_act = PENDING ;
- else if ( tp->t_act == INVOKED )
- tp->t_act = SCHEDULED ;
- }
- else /* not blocked */
- {
- if ( tp->t_act == IDLE || tp->t_act == INVOKED )
- {
- tp->t_act = SCHEDULED ;
- expired_timers[ n_expired++ ] = tp ;
- }
- }
- }
- }
-
- /*
- * Check which timers have regular expiration intervals
- */
- for ( i = 0 ; i < n_expired ; i++ )
- {
- tp = expired_timers[ i ] ;
-
- if ( ! TV_ISZERO( tp->t_interval ) )
- {
- tp->t_state = TICKING ;
- TV_ADD( tp->t_expiration, tp->t_expiration, tp->t_interval ) ;
- /*
- * We shouldn't have an insertion problem since we just extracted
- * the timers from the queue (i.e. there should be enough room)
- */
- (void) timer_pq_insert( otp->ost_timerq.tq_handle, tp ) ;
- }
- }
-
- tp = timer_pq_head( otp->ost_timerq.tq_handle ) ;
-
- if ( tp != TIMER_NULL )
- {
- struct itimerval itv ;
-
- TV_ZERO( itv.it_interval ) ;
- /*
- * Check if we had too many expired timers
- */
- if ( TV_LE( tp->t_expiration, current_time ) )
- {
- itv.it_value.tv_sec = 0 ;
- itv.it_value.tv_usec = 1 ; /* schedule an interrupt ASAP */
- /* XXX: this trick will result in another call to
- * ostimer_interrupt. So why don't we just call it
- * recursively, instead of taking another timer interrupt ?
- */
- }
- else
- TV_SUB( itv.it_value, tp->t_expiration, current_time ) ;
-
- SET_OSTIMER( otp, itv, "ostimer_interrupt" ) ;
- }
-
- #ifdef DEBUG_MSGS
- printf( "\t\t%d timers expired\n", n_expired ) ;
- #endif
-
- /*
- * Invoke the functions of all expired timers
- */
- for ( i = 0 ; i < n_expired ; i++ )
- {
- tp = expired_timers[ i ] ;
- if ( __timer_invoke( tp ) != DESTROYED &&
- ! have_env && ( tp->t_action.ta_flags & TIMER_LONGJMP ) )
- {
- (void) memcpy( (char *)ret_env,
- (char *)tp->t_action.ta_env, sizeof( jmp_buf ) ) ;
- have_env = TRUE ;
- }
- }
-
- call_level-- ;
-
- /*
- * If this is not a recursive call, and there is a jmp_buf, use it.
- */
- if ( call_level == 0 && have_env )
- {
- have_env = FALSE ;
- longjmp( ret_env, 1 ) ;
- }
- }
-
-
- /*
- * Carry out the timer action.
- * If the call level is 0
- * AND there is not already an environment to longjmp to
- * AND this timer has such an environment
- * then
- * longjmp to that environment
- *
- * Notice that all timer interrupts are blocked while this function is running.
- * Therefore, none of the global variables checked can change.
- */
- PRIVATE void invoke_protocol( tp )
- timer_s *tp ;
- {
- if ( __timer_invoke( tp ) != DESTROYED &&
- call_level == 0 && ! have_env &&
- ( tp->t_action.ta_flags & TIMER_LONGJMP ) )
- longjmp( tp->t_action.ta_env, 1 ) ;
- }
-
-
- /*
- * Add a timer to the specified OS timer's queue
- * We assume that the timer already has a valid state and action
- * associated with it.
- * We also assume that no operations (block etc) are applied to the
- * timer while this function is running.
- */
- int __ostimer_add( otp, tp, itvp, time_type )
- ostimer_s *otp ;
- register timer_s *tp ;
- struct itimerval *itvp ;
- enum timer_timetypes time_type ;
- {
- struct timeval current_time ;
- int expired ;
-
- /*
- * While this function (__ostimer_add) is running, this will be our
- * notion of the current time.
- * In reality, there may be some time skew as this function
- * is running, possibly because of swapping.
- */
- (*otp->ost_get_current_time)( ¤t_time ) ;
-
- /*
- * Since we use absolute time for our calculations,
- * convert the user-specified time to that, if necessary
- *
- * Also check if the timer has already expired. There are 2 possibilities:
- *
- * 1. a zero interval for TIMER_RELATIVE
- * 2. a time before current time for TIMER_ABSOLUTE
- *
- * Note that we always calculate t_expiration in case the user has
- * specified an it_interval.
- */
-
- if ( time_type == TIMER_RELATIVE )
- {
- /*
- * timer_start has verified that it_value is not negative
- */
- TV_ADD( tp->t_expiration, current_time, itvp->it_value ) ;
- expired = TV_ISZERO( itvp->it_value ) ;
- }
- else
- {
- tp->t_expiration = itvp->it_value ;
- expired = TV_LE( tp->t_expiration, current_time ) ;
- }
-
- tp->t_interval = itvp->it_interval ;
-
- if ( expired )
- {
- tp->t_count++ ;
- tp->t_act = SCHEDULED ;
-
- if ( TV_ISZERO( tp->t_interval ) )
- {
- invoke_protocol( tp ) ;
- return( TIMER_OK ) ;
- }
-
- /*
- * Keep expiring the timer until it exceeds the current time
- */
- time_type = TIMER_ABSOLUTE ;
- for ( ;; )
- {
- TV_ADD( tp->t_expiration, tp->t_expiration, tp->t_interval ) ;
- expired = TV_LE( tp->t_expiration, current_time ) ;
- if ( ! expired )
- break ;
- tp->t_act = SCHEDULED ;
- tp->t_count++ ;
- }
- invoke_protocol( tp ) ;
- }
-
- if ( timer_pq_insert( otp->ost_timerq.tq_handle, tp ) == PQ_ERR )
- HANDLE_ERROR( tp->t_flags, TIMER_ERR, tp->t_errnop, TIMER_ECANTINSERT,
- "TIMER __ostimer_add: can't insert timer in priority queue\n" ) ;
-
- tp->t_state = TICKING ;
-
- /*
- * Now check if we will need to set the timer again
- */
- if ( tp == timer_pq_head( otp->ost_timerq.tq_handle ) )
- {
- /*
- * Check if the user specified relative time.
- * If so, use the value given (for better accuracy), otherwise compute
- * a new value.
- * It is possible that by now the timer that was at the head
- * of the queue expired and a signal is pending. This can happen
- * if we are swapped out. The signal handling function will
- * obviously expire both the new timer and the old one, so our
- * setting a new timer value may cause a signal at a later time
- * when the queue is empty. The signal handling function should
- * be able to deal with an empty queue.
- */
- struct itimerval itv ;
-
- TV_ZERO( itv.it_interval ) ;
- if ( time_type == TIMER_RELATIVE )
- itv.it_value = itvp->it_value ;
- else
- TV_SUB( itv.it_value, tp->t_expiration, current_time ) ;
- SET_OSTIMER( otp, itv, "__ostimer_add" ) ;
- }
-
- return( TIMER_OK ) ;
- }
-
-
- /*
- * Remove the specified timer from the OS timer's queue
- * Timer interrupts should be blocked.
- */
- void __ostimer_remove( otp, tp )
- ostimer_s *otp ;
- timer_s *tp ;
- {
- struct itimerval itv ;
- struct timeval current_time ;
- timer_s *new_head_timer, *old_head_timer ;
-
- old_head_timer = timer_pq_head( otp->ost_timerq.tq_handle ) ;
- timer_pq_delete( otp->ost_timerq.tq_handle, tp ) ;
- new_head_timer = timer_pq_head( otp->ost_timerq.tq_handle ) ;
-
- if ( old_head_timer != new_head_timer )
- {
- int do_setitimer ;
-
- if ( new_head_timer != TIMER_NULL )
- {
- (*otp->ost_get_current_time)( ¤t_time ) ;
-
- /*
- * If the head_timer is less than or equal to the current time,
- * the interrupt must be pending, so we leave the OS timer running.
- * Otherwise, we restart the OS timer.
- */
- if ( TV_LE( current_time, new_head_timer->t_expiration ) )
- do_setitimer = FALSE ;
- else
- {
- do_setitimer = TRUE ;
- TV_SUB( itv.it_value, new_head_timer->t_expiration, current_time ) ;
- CHECK_INTERVAL( "__ostimer_remove", itv ) ;
- }
- }
- else /* queue is empty */
- {
- do_setitimer = TRUE ;
- TV_ZERO( itv.it_value ) ;
- }
-
- if ( do_setitimer )
- {
- TV_ZERO( itv.it_interval ) ;
- OSTIMER_SET( "__ostimer_remove", otp, itv ) ;
- }
- }
- }
-
-